package com.leyou.search.service;


import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.common.pojo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.reponsitory.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;



@Service
public class SearchService {



    @Autowired
    private BrandClient brandClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;



    private static final ObjectMapper MAPPER = new ObjectMapper();


    public Goods buildGoods(Spu spu) throws IOException {



        // 创建goods对象
        Goods goods = new Goods();

        // 查询品牌
        Brand brand = this.brandClient.queryBrandById(spu.getBrandId());



        // 查询分类名称
        List<String> names =this.categoryClient.queryCategoryNameByCid(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));



        // 查询spu下的所有sku

        List<Sku> skus = this.goodsClient.querySkuBySpuId(spu.getId());

        List<Long> prices = new ArrayList<>();

        List<Map<String, Object>> skuMapList = new ArrayList<>();

        // 遍历skus，获取价格集合

        skus.forEach(sku ->{

            prices.add(sku.getPrice());

            Map<String, Object> skuMap = new HashMap<>();

            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            skuMap.put("price", sku.getPrice());
            skuMap.put("image", StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(sku.getImages(), ",")[0] : "");

            skuMapList.add(skuMap);

        });



        // 查询出所有的搜索规格参数

        List<SpecParam> params = this.specificationClient.queryGroupsByGid(null, spu.getCid3(), null, true);

        // 查询spuDetail。获取规格参数值

        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spu.getId());

        // 获取通用的规格参数

        Map<Long, Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {

        });

        // 获取特殊的规格参数

        Map<Long, List<Object>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {

        });

        // 定义map接收{规格参数名，规格参数值}

        Map<String, Object> paramMap = new HashMap<>();

        params.forEach(param -> {

            // 判断是否通用规格参数

            if (param.getGeneric()) {

                // 获取通用规格参数值

                String value = genericSpecMap.get(param.getId()).toString();

                // 判断是否是数值类型

                if (param.getNumeric()){

                    // 如果是数值的话，判断该数值落在那个区间

                    value = chooseSegment(value, param);

                }

                // 把参数名和值放入结果集中

                paramMap.put(param.getName(), value);

            } else {

                paramMap.put(param.getName(), specialSpecMap.get(param.getId()));

            }

        });



        // 设置参数

        goods.setId(spu.getId());

        goods.setCid1(spu.getCid1());

        goods.setCid2(spu.getCid2());

        goods.setCid3(spu.getCid3());

        goods.setBrandId(spu.getBrandId());

        goods.setCreateTime(spu.getCreteTime());

        goods.setSubTitle(spu.getSubTitle());

        goods.setAll(spu.getTitle() + brand.getName() + StringUtils.join(names, " "));

        goods.setPrice(prices);

        goods.setSkus(MAPPER.writeValueAsString(skuMapList));

        goods.setSpecs(paramMap);



        return goods;

    }



    private String chooseSegment(String value, SpecParam p) {

        double val = NumberUtils.toDouble(value);

        String result = "其它";

        // 保存数值段

        for (String segment : p.getSegments().split(",")) {

            String[] segs = segment.split("-");

            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }

            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }

        return result;

    }

        @Autowired
        private GoodsRepository goodsRepository;



    public void createIndex(Long id) throws IOException{
        Spu spu = goodsClient.querySpuById(id);
        //构建商品
        Goods goods = this.buildGoods(spu);
        //保存数据到索引库
        this.goodsRepository.save(goods);
    }

    public void deleteIndex(Long id) {
            this.goodsRepository.deleteById(id);
    }


    public SearchResult search(SearchRequest request) {
        if(StringUtils.isBlank(request.getKey())){
            return null;
        }
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //QueryBuilder basicQuery = QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND);
        BoolQueryBuilder basicQuery = buildBoolQueryBuilder(request);
        queryBuilder.withQuery(basicQuery);

        queryBuilder.withPageable(PageRequest.of(request.getPage()-1,request.getSize()));
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));

        String categoryAggName = "categories";
        String brandAggName = "brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        AggregatedPage<Goods> search = (AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());

        //获取聚合结果集并解析
        List<Map<String,Object>> categories = getCategoryAggResult(search.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(search.getAggregation(brandAggName));

        //判断是否一个分类，只有一个分类才做规格参数的聚合
        List<Map<String,Object>> specs = null;
        if(!CollectionUtils.isEmpty(categories) && categories.size()==1){
            //对规格参数进行聚合
            specs = getParamAggResult((Long)categories.get(0).get("id"),basicQuery);
        }

        return new SearchResult(search.getTotalElements(),
                Integer.parseInt(search.getTotalPages()+""),
                search.getContent(),
                categories,
                brands,
                specs);
    }

    /*
       构建bool查询
        */
    private BoolQueryBuilder buildBoolQueryBuilder(SearchRequest request) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //基本查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //过滤条件
        Map<String, Object> filter = request.getFilter();
        for (Map.Entry<String, Object> entry : filter.entrySet()) {
            String key = entry.getKey();
            if(StringUtils.equals("品牌",key)){
                key="brandId";
            }else if(StringUtils.equals("分类",key)){
                key="cid3";
            }else{
                key="specs."+key+".keyword";
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
        }
        return  boolQueryBuilder;
    }

    //根据查询条件聚合规格参数
    private List<Map<String,Object>> getParamAggResult(Long cid, BoolQueryBuilder basicQuery) {

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(basicQuery);

        //查询要聚合的规格参数
        List<SpecParam> params = specificationClient.queryGroupsByGid(null, cid, null, true);

        //添加规格参数的聚合
        params.forEach(param->{
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs."+param.getName()+".keyword"));
        });

        //添加结果集过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));

        //执行聚合查询
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());

        //解析聚合结果集
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        List<Map<String,Object>> specs = new ArrayList<>();
        for(Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()){
            Map<String,Object> map = new HashMap<>();
            map.put("k",entry.getKey());

            StringTerms terms = (StringTerms)entry.getValue();
            List<Object> options = new ArrayList<>();
            terms.getBuckets().forEach(bucket -> {
                options.add(bucket.getKeyAsString());
            });
            map.put("options",options);
            specs.add(map);
        }

        return specs;

    }

    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        LongTerms terms = (LongTerms)aggregation;

        List<Brand> brands = new ArrayList<>();
        terms.getBuckets().forEach(bucket -> {
            long l = bucket.getKeyAsNumber().longValue();
            Brand brand = brandClient.queryBrandById(l);
            brands.add(brand);
        });

        return brands;
    }

    private List<Map<String,Object>> getCategoryAggResult(Aggregation aggregation) {
        LongTerms terms = (LongTerms)aggregation;
        List<Map<String,Object>> list = new ArrayList<>();
        terms.getBuckets().forEach(bucket -> {
            long l = bucket.getKeyAsNumber().longValue();
            List<String> names = categoryClient.queryCategoryNameByCid(Arrays.asList(l));
            Map<String,Object> map = new HashMap<>();
            map.put("id",l);
            map.put("name",names.get(0));
            list.add(map);
        });
        return list;
    }

}

